Skip to content

HIVE-29698: Replace ESRI geometry library with JTS in geospatial - #6581

Merged
ayushtkn merged 8 commits into
apache:masterfrom
ayushtkn:HIVE-29698
Jul 27, 2026
Merged

HIVE-29698: Replace ESRI geometry library with JTS in geospatial#6581
ayushtkn merged 8 commits into
apache:masterfrom
ayushtkn:HIVE-29698

Conversation

@ayushtkn

@ayushtkn ayushtkn commented Jul 6, 2026

Copy link
Copy Markdown
Member

What changes were proposed in this pull request?

Migrate the geospatial plugin from ESRI geometry-api-java to JTS (Java Topology Suite) as the core geometry library.

Why are the changes needed?

JTS is more widely used, better maintained, and provides the foundation for upcoming Iceberg geometry type support.

Key design decisions:

  1. On-disk serialization is unchanged. The geometry transport bytes (stored in the binary column that backs a geometry) keep the existing layout: 4-byte WKID (little-endian) + 1-byte OGC type tag + ESRI shape body. No new format is introduced — an earlier revision of this PR added a SRID | 0xFF | WKB layout, but it was dropped so the bytes stay identical to those written by the previous ESRI-based version and remain readable by other engines that consume the raw bytes.
  2. Reads parse this single format. Geometry data written by the previous ESRI-based version stays readable, including legacy shapes carrying a Z and/or M dimension (ESRI shape types 21/23/25/28 and the optional trailing M block on the Z types are handled on read).
  3. ESRI shape encoding remains writable. ST_AsShape writes it and ST_GeomFromShape reads it, as standalone serializations analogous to ST_AsBinary/ST_GeomFromWKB for WKB.
  4. Native (de)serialization. ESRI-shape ↔ JTS and Esri-JSON ↔ JTS conversion is reimplemented in EsriShapeConverter and EsriJsonConverter, removing the ESRI library dependency for these paths. The XY, Z, and M shape-type variants are all both read and written, so Z and M ordinates round-trip.
  5. ESRI library retained for one function. ST_GeodesicLengthWGS84 delegates its WGS84 ellipsoidal (geodesic) distance to ESRI's GeometryEngine.geodesicDistanceOnWGS84 rather than reimplementing Vincenty's formula; the library is bundled into hive-exec via the shade plugin for this single use.
  6. Near-identical results. JTS produces nearly identical results, with small expected numeric differences (e.g. coordinate rounding at the last significant digits, normalized via qt:replace in the golden output).

Does this PR introduce any user-facing change?

Minimal, and best-effort compatible:

  1. The on-disk byte layout of geometry values is unchanged (WKID | type | ESRI-shape), so both reads and writes remain backward- and forward-compatible: data written by this version is still readable by another engine — or an older Hive — that reads the raw bytes, and legacy data (Z and M included) is read transparently. For portable interchange, ST_AsBinary (WKB) and ST_AsGeoJson are unchanged.
  2. Minor numeric differences in some results due to the library change (e.g. coordinate values rounding at the last significant digits).

How was this patch tested?

CI & Manually on docker

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates Hive’s geospatial (Esri-json / ST_* UDF) implementation to use JTS (Java Topology Suite) as the primary geometry model, while retaining Esri JSON/shape interoperability where required. It updates serialization/deserialization, rewires many UDFs to JTS operations, and adjusts tests/results to match JTS output.

Changes:

  • Replace many com.esri.* geometry usages in UDFs/SerDes with org.locationtech.jts.* equivalents, including prepared-geometry acceleration for relational predicates.
  • Introduce JTS-based Esri JSON conversion (EsriJsonConverter) and adjust Esri JSON factory/feature-class mapping.
  • Update Maven dependencies (add JTS) and refresh golden test outputs for geospatial UDFs.

Reviewed changes

Copilot reviewed 111 out of 111 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/SpatialReferenceJsonSerializer.java Serialize WKID as JSON spatialReference object for JTS-based model.
serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryTypeJsonSerializer.java Serialize Esri geometryType as a canonical esriGeometry* string.
serde/src/java/org/apache/hadoop/hive/serde2/esriJson/serializer/GeometryJsonSerializer.java Serialize JTS Geometry to Esri JSON via WKB round-trip through Esri engine.
serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/SpatialReferenceJsonDeserializer.java Parse spatialReference object into integer WKID.
serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryTypeJsonDeserializer.java Parse geometryType JSON string into canonical esriGeometry* string.
serde/src/java/org/apache/hadoop/hive/serde2/esriJson/deserializer/GeometryJsonDeserializer.java Parse Esri JSON to JTS Geometry via Esri parser + WKB conversion.
serde/pom.xml Add jts-core dependency for serde module.
ql/src/test/results/clientpositive/llap/geospatial_udfs.q.out Update expected outputs due to JTS numeric formatting / geometry differences.
ql/src/test/queries/clientpositive/geospatial_udfs.q Add output normalization for floating-point stability across JDKs.
ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStGeomFromShape.java Update tests to assert JTS geometry properties/SRID instead of Esri geometry equality.
ql/src/test/org/apache/hadoop/hive/ql/udf/esri/TestStCentroid.java Update centroid assertions to compare numeric coordinates rather than Esri Point.
ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestGeoJsonSerDe.java Update SerDe tests to construct expected geometries using JTS.
ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/TestEsriJsonSerDe.java Update Esri JSON SerDe tests to use JTS geometry creation.
ql/src/test/org/apache/hadoop/hive/ql/udf/esri/serde/JsonSerDeTestingBase.java Update SerDe test helpers to validate JTS geometry outputs.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Z.java Switch Z-accessor to JTS coordinate Z handling.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Y.java Switch Y-accessor to JTS point Y.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_X.java Switch X-accessor to JTS point X.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Within.java Implement within using JTS (and prepared geometry path).
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Union.java Implement union using JTS UnaryUnionOp.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Touches.java Implement touches using JTS (and prepared geometry path).
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_SymmetricDiff.java Implement symmetric difference using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_StartPoint.java Implement start point extraction using JTS LineString.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Relate.java Implement DE-9IM relate using JTS relate.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Polygon.java Construct/parse polygon via JTS WKT reader and type checks.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PolyFromWKB.java Parse polygon from WKB using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointZ.java Construct Z/M point using JTS coordinate variants.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointN.java Extract Nth point from line/multipoint using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointFromWKB.java Parse point from WKB using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Point.java Construct/parse point using JTS coordinates and WKT reader.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Overlaps.java Implement overlaps using JTS (and prepared geometry path).
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumPoints.java Use JTS getNumPoints().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumInteriorRing.java Use JTS polygon interior ring count.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_NumGeometries.java Use JTS getNumGeometries() for multi-geometries.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPolygon.java Parse MultiPolygon via JTS WKT reader.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiPoint.java Construct/parse MultiPoint via JTS geometry factory / WKT reader.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MultiLineString.java Construct/parse MultiLineString via JTS geometry factory / WKT reader.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPolyFromWKB.java Parse multi-polygon from WKB using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPointFromWKB.java Parse multi-point from WKB using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MLineFromWKB.java Parse multi-line from WKB using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinZ.java Compute min Z by iterating coordinates in JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinY.java Use JTS envelope minY; handle empty geometry.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinX.java Use JTS envelope minX; handle empty geometry.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MinM.java Compute min M by iterating coordinates in JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxZ.java Compute max Z by iterating coordinates in JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxY.java Use JTS envelope maxY; handle empty geometry.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxX.java Use JTS envelope maxX; handle empty geometry.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MaxM.java Compute max M by iterating coordinates in JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_M.java Read M ordinate from JTS point coordinate.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineString.java Construct/parse LineString using JTS geometry factory / WKT reader.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineFromWKB.java Parse LineString from WKB using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Length.java Use JTS getLength().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsSimple.java Use JTS isSimple().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsRing.java Implement ring check using JTS LineString.isClosed/isSimple.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsMeasured.java Implement measured check using JTS coordinate ordinates.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsEmpty.java Use JTS isEmpty().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_IsClosed.java Implement closed check for LineString/MultiLineString using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Is3D.java Implement 3D check using JTS coordinate ordinates.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersects.java Implement intersects using JTS (and prepared geometry path).
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Intersection.java Implement intersection using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_InteriorRingN.java Extract polygon hole ring using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromWKB.java Parse arbitrary geometry from WKB using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromText.java Parse WKT into JTS geometry and set SRID.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromShape.java Convert Esri shape bytes to JTS geometry via converter.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromJson.java Parse Esri JSON to JTS via converter and add cleanup on close.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromGeoJson.java Parse GeoJSON to JTS via GeoJSON reader and add cleanup on close.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryRelational.java Replace Esri acceleration with JTS PreparedGeometry.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeometryN.java Implement geometryN extraction via JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomCollection.java Parse geometry collections via JTS WKT reader.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeodesicLengthWGS84.java Reimplement geodesic length using JTS coordinate sequences + Haversine.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ExteriorRing.java Return exterior ring as LineString using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Equals.java Implement topological equality via JTS equalsTopo.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EnvIntersects.java Implement envelope intersects via JTS envelopes.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Envelope.java Return envelope geometry using JTS getEnvelope().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_EndPoint.java Implement end point extraction using JTS LineString.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_DistanceSphere.java Compute great-circle distance using JTS points + Haversine.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Distance.java Implement distance using JTS distance().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Disjoint.java Implement disjoint using JTS (and prepared geometry path).
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Dimension.java Use JTS getDimension().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Difference.java Implement difference using JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Crosses.java Implement crosses using JTS (and prepared geometry path).
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_CoordDim.java Infer coordinate dimension using JTS coordinate Z/M.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_ConvexHull.java Implement convex hull using JTS (with GeometryCollection fallback).
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Contains.java Implement contains using JTS (and prepared geometry path).
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Centroid.java Implement centroid using JTS getCentroid().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Buffer.java Implement buffer using JTS buffer().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Boundary.java Implement boundary using JTS and normalize LinearRing to LineString.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_BinEnvelope.java Use JTS Envelope and return polygon geometry for bins.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Bin.java Use JTS Point to compute bin IDs.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsText.java Output WKT via JTS writers and normalize dimension spacing.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsShape.java Convert JTS geometry to Esri shape bytes via converter.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsJson.java Convert JTS geometry to Esri JSON via converter.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsGeoJson.java Output GeoJSON using JTS GeoJSON writer.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_AsBinary.java Output WKB using dimension-aware JTS writer.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Area.java Use JTS getArea().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Union.java Aggregate union using JTS UnaryUnionOp.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_Intersection.java Aggregate intersection using iterative JTS intersections.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Aggr_ConvexHull.java Aggregate convex hull using JTS GeometryCollection.convexHull().
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/GeoJsonSerDe.java Update GeoJSON SerDe to use JTS readers/writers.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/EsriJsonSerDe.java Update Esri JSON SerDe to use EsriJsonConverter for parsing/serialization.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/serde/BaseJsonSerDe.java Generalize SerDe base from OGCGeometry to JTS Geometry.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/HiveGeometryOIHelper.java Update OI helper to return JTS Geometry/Point and parse WKT via JTS.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/Haversine.java Switch distance helper from Esri OGCPoint to JTS Point.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/EsriJsonConverter.java Add standalone JTS <-> Esri JSON conversion without ESRI geometry library dependency.
ql/src/java/org/apache/hadoop/hive/ql/udf/esri/BinUtils.java Switch bin envelope type to JTS Envelope and adjust query methods to return envelopes.
ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriJsonFactory.java Register only Geometry (JTS) serializers/deserializers; move other mappings to field annotations.
ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeatureClass.java Update feature-class schema to use String geometryType + int WKID with field-level (de)serializers.
ql/src/java/org/apache/hadoop/hive/ql/io/esriJson/EsriFeature.java Switch feature geometry field to JTS Geometry.
ql/pom.xml Replace ESRI dependency with JTS core + io-common in ql module and shade includes.
pom.xml Add jts.version property.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PointFromWKB.java Outdated
Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_LineFromWKB.java Outdated
Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromWKB.java Outdated
Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_GeomFromShape.java Outdated
Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_Is3D.java Outdated
Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MLineFromWKB.java Outdated
Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPointFromWKB.java Outdated
Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_MPolyFromWKB.java Outdated
Comment thread ql/src/java/org/apache/hadoop/hive/ql/udf/esri/ST_PolyFromWKB.java Outdated

/**
* Golden bytes hand-encoded from the ESRI Shapefile spec (little-endian), so this checks
* the reader against the on-disk format itself rather than only against our own writer.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note about spec vs our writer: your current code may not handle some cases that are ok according to the the spec. An example I know are (multi)polygons - the spec does not mandate any ordering among external / internal rings, while your reader relies on hulls being followed by their holes. AFAIK the old lib will also keep this convention, so it is ok to assume it, but based on the lib and not the spec.


/**
* Computes the geodesic length of a single LineString by summing Haversine
* distances between consecutive vertices. Iterates the CoordinateSequence

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that it is ok to call this ST_GeodesicLengthWGS84 with Haversine formula as geodesic + WGS84 implies ellipsoid. AFAIK the Esri lib uses Vincenty underneath, which is not included in JTS.

I would
a: rename this function to ST_LengthSphere (redshift has this: https://docs.aws.amazon.com/redshift/latest/dg/ST_LengthSphere-function.html)
b: keep the ESRI geometry library to fill this gap in JTS until a replacement lib is found for ellipsoid computations

Note that the Esri implementation also sums the segment length in more sophisticated way to reduce precision loss when adding doubles. jts also has tools for this: https://github.com/locationtech/jts/blob/master/modules/core/src/main/java/org/locationtech/jts/math/DD.java
Probably this is not that important, but can be another source of small differences.

A bigger issue IMO is that this function is not tested at all. Impala has a few tests generated from the original spatail framwork for Hadoop tests: https://github.com/apache/impala/blob/780e6683a21dae3622744a82f92e155ae06e13f2/testdata/workloads/functional-query/queries/QueryTest/geospatial-esri.test#L397

}

@Test
public void testMultiPolygonRoundTrip() throws Exception {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to add multipolygon with holes, as it the most complex case to (de)serialize due to ring orientation rules.

Geometry back = roundTrip(text);
assertTrue("Expected a MultiPolygon!", back instanceof MultiPolygon);
assertEquals(2, back.getNumGeometries());
assertTrue(back.equalsTopo(wkt(text)));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for using equalsTopo? It can hide subtle differences like reordering rings. Not sure if it enforces ring orientation.

IMO equalsExact should be also true here and could catch more bugs. It has a version with tolerance to accept small rounding of coordinates:
https://locationtech.github.io/jts/javadoc/org/locationtech/jts/geom/Geometry.html#equalsExact-org.locationtech.jts.geom.Geometry-double-

-- Truncate floating-point numbers to 12 decimal places for cross-platform reproducibility
-- (JTS buffer uses Math.cos/sin which can differ by 1 ULP across JDK implementations)
--! qt:replace:/(\d+\.\d{12})\d+/$1/
-- create a table with two columns one for each point.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recently created HIVE-29726 about a bug in st_intersects() on multilinestrings. The bug comes from the ESRI lib, so this patch should fix it. Do you think that it makes sense to create a PR for that before this change is merged, or lets fix the issue with that?

Anyway, it would be nice to have a regression test for it, so that the following query returns false:
select st_intersects(st_geomfromtext('LINESTRING(1 0, 1 1)'), st_geomfromtext('MULTILINESTRING((0 0, 0 1), (2 0, 2 1))'));

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't pull in that change in this, but we can independently have a test, if you already have one as a separate PR after this, to increase the test coverage

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack


OGCGeometry bufferedGeometry = ogcGeometry.buffer(distance.get());
// TODO persist type information (polygon vs multipolygon)
Geometry bufferedGeometry = geom.buffer(distance.get(), 24);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24 quadrantSegments is chosen to match Esri lib behavior? A comment could mention this.
(this change is familiar to me from Trino PR description: trinodb/trino#27881)

@@ -17,14 +17,13 @@
*/
package org.apache.hadoop.hive.ql.udf.esri;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you plan to rename the package? At this point there is not much Esri about these functions anymore. Probably best done in another commit to reduce noise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't plan to do this as part of this PR, considering compat, what are you folks doing? In case you had any packages with esri or so?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impala's handling of this changed recently, I copied the whole dir from Hive to Impala to be able to make modifications:
apache/impala@d51c3c1

So from Impala's point of view Hive is free to modify / rename / delete these functions.
My plan is to keep the original ESRI lib around for a while to provide compatibility for old workloads, and deprecate it at same point when the new lib seems stable.


public static final int WKID_UNKNOWN = 0;

// Magic byte at offset 4 to distinguish new WKB format from old ESRI format.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a better solution, but I am worried about interoperability. If there are actually tables with the old format, and users insert to those after this change, then we'll have mixed tables. Reading those will be really painful with any engine other than Hive.

Is there are function that just reads a BINARY and writes it out in the the new format? Would be useful to rewrite tables to get rid of the old format. ST_GeomFromShape does only read old format, right?

In Impala I made the choice of Esri shape vs new format (WKB) a startup flag in https://gerrit.cloudera.org/#/c/24549/ . This is less convenient than accepting both, but I see it as a temporary solution before adding GEOMETRY type and I don't want to commit to a custom format for that time.

What is the plan in Hive about adding GEOMETRY type? That would be an unavoidable breaking change, as functions that return BINARY shapes would need to return geometry (e.g. st_point), which would break queries that relied on the type being BINARY. Adding this new format now would mean that you'll need to be able to read it later, e.g by having ST_GeomFromFromNewHiveShape() (or accepting both formats in ST_GeomFromShape() ).

* @return a JTS Geometry, or {@code null} for the Null/Unknown shape type
* @throws IllegalArgumentException if the buffer contains an unsupported or malformed shape
*/
public static Geometry fromEsriShape(ByteBuffer shapeBuffer) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit inconsistent that this function expects the shape file format part of a shape, while geometryFromEsriShape expects the header. Could be renamed, e.g. to fromEsriShapeBody.

return GeometryUtils.geometryToEsriShapeBytesWritable(geometry, wkid, OGCType.UNKNOWN);
ByteBuffer shapeBuffer = ByteBuffer.wrap(shape.getBytes(), 0, shape.getLength())
.order(ByteOrder.LITTLE_ENDIAN);
Geometry jtsGeom = EsriShapeConverter.fromEsriShape(shapeBuffer);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function reads only the old format, right? Can you add a comment about that?

@ayushtkn ayushtkn changed the title HIVE-29698: Replace ESRI geometry library with JTS in geospatial plugin HIVE-29698: Replace ESRI geometry library with JTS in geospatial Jul 23, 2026
/**
* Ellipsoidal geodesic distance between two lon/lat points on the WGS84 spheroid,
* using Vincenty's inverse formula (the same ellipsoidal model the ESRI library used,
* so results match the previous implementation). Coordinates are in degrees, result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be simpler to keep to ESRI lib for this functionality? https://github.com/Esri/geometry-api-java/blob/master/src/main/java/com/esri/core/geometry/GeoDist.java has its own Vincenty, or the same function could be used an in the original UDF.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reverted the reimplementation. ST_GeodesicLengthWGS84 delegates to GeometryEngine.geodesicDistanceOnWGS84.

private static void assertEqualsNormalized(Geometry expected, Geometry actual) {
Geometry e = expected.copy();
Geometry a = actual.copy();
e.normalize();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is normalization really needed? The round trips should not change the order of coordinates. Also, fixing orientation can actually hide bugs.

I still think that equalsExact with tolerance is the right way to check round trips. The order of coordinates can matter, e.g. when representing a route with a linestring reversing it change semantics.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped normalization. Round-trip checks now use equalsExact(expected, actual, EPSILON) directly, so coordinate order and ring orientation must be preserved.

-- Truncate floating-point numbers to 12 decimal places for cross-platform reproducibility
-- (JTS buffer uses Math.cos/sin which can differ by 1 ULP across JDK implementations)
--! qt:replace:/(\d+\.\d{12})\d+/$1/
-- create a table with two columns one for each point.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack

private static final int TYPE_POLYGON_M = 25;
private static final int TYPE_MULTIPOINT_M = 28;

// ESRI marks an absent measure with any value less than this sentinel.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses <=
Which one is correct? Based on googling it seems that ==-1e38 is also considered no data.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<= is correct on read — kept it. Exactly -1e38 is the no-data marker written by ArcView/GDAL and others, and a real measure is never exactly -1e38, so <= catches that marker as well as any value below it. The only actual bug was on the write side: absent M was written as -1e38, which a strict-< reader (e.g. ESRI's own Interop.translateFromAVNaN) would read back as a valid measure. Changed the write value to -Double.MAX_VALUE (matching ESRI's translateToAVNaN), which every reader interprets as no-data.

@sonarqubecloud

Copy link
Copy Markdown

@csringhofer csringhofer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR's text no longer matches what the change does. Otherwise looks good to me.

@abstractdog
abstractdog self-requested a review July 27, 2026 08:40

@abstractdog abstractdog left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

thanks @csringhofer, for chiming in with geospatial background, my high-level review was not accurate, I wish we could always collaborate with other engines in those topics that sometimes has only a single person per engine :)

and thanks @ayushtkn for addressing all the comments

@ayushtkn
ayushtkn merged commit ba5dc5c into apache:master Jul 27, 2026
5 checks passed
@ayushtkn

Copy link
Copy Markdown
Member Author

Thanx @abstractdog and @csringhofer for the reviews!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants